Skip to content

feat: Command Palette (⌘K) — chat actions framework#1323

Draft
wislap wants to merge 28 commits into
mainfrom
topic/chat-actions
Draft

feat: Command Palette (⌘K) — chat actions framework#1323
wislap wants to merge 28 commits into
mainfrom
topic/chat-actions

Conversation

@wislap
Copy link
Copy Markdown
Contributor

@wislap wislap commented May 12, 2026

Summary

Add a unified Command Palette (⌘K) to the chat window, allowing users to search and execute plugin actions without leaving the conversation.

Changes

SDK enhancements

  • PluginSettings base class: callable json_schema_extra support, __neko_hot__ metadata marker for robust hot-field detection
  • @quick_action decorator: bidirectional compatibility (works above or below @plugin_entry)
  • NekoPluginBase: add get_user_language() / fetch_user_language() / get_attachments() / export_push_image() (fixes feat: add lifekit plugin #1281 missing methods)
  • SystemInfo.get_user_language(): reuses existing get_system_config() infrastructure

Actions backend (plugin-server)

  • ActionAggregationService: collects actions from all providers
  • SettingsActionProvider: auto-generates toggle/slider/dropdown actions from PluginSettings hot fields
  • ListActionsProvider: maps plugin list_actions metadata to descriptors
  • SystemActionProvider: entry buttons + static UI navigation per plugin
  • BuiltinActionsProvider: static built-in actions
  • ActionExecutionService: dispatches execution (hot-config update via hot_update_plugin_config, entry trigger, lifecycle via PluginLifecycleService)
  • PreferencesService: per-user pin/hide/recent persistence
  • Routes: GET /chat/actions, POST /chat/actions/{id}/execute, GET/POST /chat/actions/preferences
  • GET /plugin/{id}/settings/schema endpoint

Main-server proxy

  • actions_proxy_router: forwards /api/actions/* from main FastAPI app to plugin-server (required because React chat iframe can only reach the main port)

Frontend (React chat)

  • CommandPalette.tsx: searchable action list, keyboard navigation, inline controls (toggle/slider/dropdown/number/text/button), category grouping, pin/hide context menu
  • message-schema.ts: typed postMessage protocol between host page and React chat
  • app-react-chat-window.js: bridge ⌘K shortcut and action data to the iframe

Tests

  • 8 test files covering models, providers, execution service, proxy router, integration routes, and frontend component

Architecture notes

  • No new IPC channels or transport layers — reuses hot_update_plugin_config, PluginLifecycleService, state.plugin_hosts, push_message v2, SystemInfo.get_system_config()
  • The proxy router follows the same pattern as system_router forwarding to memory_server
  • 57 sync tests pass; 21 async tests require pytest-asyncio (CI has it)

Summary by CodeRabbit

  • 新功能
    • 聊天窗口新增“快速操作/命令面板”,支持⌘按钮、以“/”触发、参数表单、注入文本、导航与偏好管理。
  • 后端
    • 增加动作聚合、执行与偏好服务及代理转发路由,支持故障降级、路径编码和 recent/pinned/hidden 行为。
  • 平台/SDK
    • 插件 SDK/运行时新增导出/附件与用户语言接口,并改进设置解析与热更新能力。
  • 测试
    • 大量单元与集成测试覆盖面板、路由、提供者与执行逻辑。
  • 样式
    • 命令面板完整样式、深色主题与减少动画支持。

Review Change Stack

LocalBuilder added 7 commits May 12, 2026 19:48
- Add PluginSettings(BaseModel) and SettingsField(hot=True) for typed
  plugin configuration with hot-update field markers
- Add @quick_action decorator that works both above and below @plugin_entry
- Add 'chat_command' to EntryKind literal type
- Export PluginSettings, SettingsField, quick_action from plugin SDK surface
- Refactor llm_tool import to avoid reload shadowing
- ActionAggregationService: collects actions from all providers
- ActionExecutionService: dispatches action execution (hot-config toggle,
  plugin entry trigger, lifecycle control, navigation)
- ListActionsProvider: maps plugin list_actions metadata to descriptors
- SettingsActionProvider: auto-generates toggle/slider/dropdown actions
  from PluginSettings hot=True fields
- SystemActionProvider: lifecycle start/stop/restart actions per plugin
- BuiltinActionsProvider: static built-in actions (e.g. open settings)
- PreferencesService: per-user action pinning/hiding persistence
- ActionDescriptor / UserActionPreferences domain models
- GET /actions, POST /actions/execute, GET/PUT /actions/preferences routes
- GET /plugin/{id}/settings/schema endpoint
- plugin_settings_resolver infrastructure helper
- Wire actions_router into http_app and routes/__init__
Add actions_proxy_router that forwards /api/actions/* requests from the
main FastAPI app to the plugin server, enabling the React chat frontend
to access the actions API without CORS issues.

Endpoints proxied:
- GET  /api/actions         → list available actions
- POST /api/actions/execute → execute an action by id
- GET  /api/actions/preferences → get user action preferences
- PUT  /api/actions/preferences → update user action preferences
- CommandPalette.tsx: searchable action list with keyboard navigation,
  category grouping, parameter input forms, execution feedback
- message-schema.ts: typed message protocol between host and React chat
- App.tsx: mount CommandPalette, handle toggle via postMessage
- styles.css: palette overlay, animations, dark/light theme support
- app-react-chat-window.js: bridge postMessage events to React chat
- Tests for CommandPalette rendering and message schema validation
- Integration: test_action_routes.py (full HTTP round-trip)
- Unit: aggregation, execution, list_actions_provider, models,
  settings_provider, system_provider
- Unit: actions_proxy_router (main server proxy)
# Conflicts:
#	plugin/sdk/plugin/__init__.py
#	plugin/sdk/plugin/settings.py
#	plugin/sdk/shared/core/decorators.py
…ginBase

- get_user_language() / set_user_language() / fetch_user_language()
- get_attachments() for image attachments from chat messages
- export_push_image() for image export
- Simplify data_path() to use config_dir / 'data' directly

Fixes lifekit 'get_user_language' AttributeError after #1281 merge.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1fb6cead-5f15-4d6b-99dd-e0acd6e8ca07

📥 Commits

Reviewing files that changed from the base of the PR and between 4afbf0c and cbb1826.

📒 Files selected for processing (1)
  • app/main_server.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/main_server.py

Walkthrough

新增端到端快速操作(Command Palette):前端组件与样式、消息模式与宿主集成;主服 actions-proxy;插件服务器域模型、多个 ActionProvider、聚合/执行/偏好服务及路由;包含 SDK 与宿主增强与全面测试喵。

Changes

Quick Actions — 单一功能群

Layer / File(s) Summary
前端:CommandPalette 组件与 App 集成
frontend/react-neko-chat/src/CommandPalette.tsx, frontend/react-neko-chat/src/App.tsx
新增 CommandPalette 组件与导出类型,App 集成面板打开/关闭、斜杠触发、注入文本与导航、执行与偏好回调喵。
前端:消息模式(Zod)与类型测试
frontend/react-neko-chat/src/message-schema.ts, frontend/react-neko-chat/src/message-schema.test.ts
为快速操作添加 Zod 模式(CommandItem/Preferences),扩展 chatWindowPropsSchema 并新增解析/回调测试喵。
前端:样式 与 组件测试
frontend/react-neko-chat/src/styles.css, frontend/react-neko-chat/src/test/CommandPalette.test.tsx, frontend/react-neko-chat/src/test/setup.ts
新增 Command Palette 的 .cp-* CSS 子系统与动画,及 Vitest/RTL 覆盖渲染、搜索、控件与交互测试,并调整 ResizeObserver 测试存根喵。
宿主与静态集成
static/app-react-chat-window.js
宿主缓存/获取/执行快速操作并持久化偏好,窗口打开與插件状态变更时刷新,向 React 窗口注入 quickActions 数据与回调喵。
主服务器代理路由
main_routers/actions_proxy_router.py, app/main_server.py, tests/unit/test_actions_proxy_router.py
在主服务器注册 actions-proxy 路由,探测并短期缓存用户插件服务器基址,转发 /chat/actions 系列请求並在失败时降级或返回 HTTP 502;包含代理路由单元测试喵。
后端域模型 与 协议
plugin/server/domain/action_models.py, plugin/server/domain/action_provider.py
新增 Pydantic 模型:ActionDescriptor、ActionExecuteRequest/Response、UserActionPreferences;定义 ActionProvider 协议喵。
操作提供者实现
plugin/server/application/actions/{settings,system,list,builtin}_provider.py
实现 SettingsActionProvider、SystemActionProvider、ListActionsProvider、BuiltinActionsProvider,从设置、入口/事件、插件元数据與内置逻辑生成 ActionDescriptor 列表喵。
服务层:聚合/执行/偏好
plugin/server/application/actions/{aggregation,execution,preferences}_service.py
实现 ActionAggregationService(多提供者聚合)、ActionExecutionService(路由並执行设置/系统/列表动作)、PreferencesService(JSON 文件持久化、原子写入、recent 管理与锁保护)喵。
路由、配置端点 与 解析器
plugin/server/routes/actions.py, plugin/server/routes/config.py, plugin/server/http_app.py, plugin/server/routes/__init__.py, plugin/server/infrastructure/plugin_settings_resolver.py
新增插件服务器路由:列出动作、获取/保存偏好、执行动作;新增 /plugin/{plugin_id}/settings/schema 用于返回插件设置模式,并在 http_app 中注册 actions 路由喵。
SDK / 插件基类增强
plugin/sdk/plugin/__init__.py, plugin/sdk/plugin/base.py, plugin/sdk/plugin/settings.py, plugin/sdk/shared/core/decorators.py, plugin/sdk/shared/runtime/system_info.py
动态导入 llm_tool、改进 SettingsField hot 标记與默认值计算、NekoPluginBase 新增附件/语言方法、quick_action 装饰器兼容多种书写顺序并直接更新元数据、增加 SystemInfo.get_user_language 接口喵。
测试:单元 与 集成
plugin/tests/*, tests/unit/test_actions_proxy_router.py
新增大量单元與集成测试,覆盖聚合、执行、提供者、模型、路由、代理與偏好持久化,以及错误/边界路径喵。

Estimated code review effort:
🎯 4 (Complex) | ⏱️ ~45 minutes

"小诗喵" — 诗歌庆祝(5 行):
快速面板登场喵,前后端同舞忙喵~
斜杠一触命令现喵,注入与偏好随手藏喵~
代理稳健降级护喵,插件路由通四方喵~
SDK 与宿主握爪笑喵,测试护航保平安喵~
功能成形喵,喵星人也要鼓掌一声喵~

Comment thread frontend/react-neko-chat/src/CommandPalette.tsx Fixed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d6125a364

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +404 to +407
const hasInlineWidget = item.type === 'instant' && (
item.control === 'toggle' || item.control === 'entry_toggle' ||
item.control === 'dropdown' || item.control === 'slider' || item.control === 'number'
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat text controls as inline widgets

When a hot string setting is rendered, InlineWidget still places the text input in the row, but hasInlineWidget is false, so the row installs its button-style onKeyDown handler. While the input is focused, pressing Space bubbles to the row, which calls preventDefault(), so users cannot type spaces into text setting values. Include text in the inline-control set or stop propagation from the text input.

Useful? React with 👍 / 👎.

Comment on lines +91 to +96
updates: dict[str, object] = {toml_section: {field_name: value}}

result = await hot_update_plugin_config(
plugin_id=plugin_id,
updates=updates,
mode="temporary",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject settings actions that were not exposed

The execute route dispatches settings updates solely from the path, so any caller can craft plugin:settings:<field> and this code will send a temporary config update even when the plugin has no PluginSettings class or when <field> is not marked hot=True and was never returned by SettingsActionProvider. That bypasses the intended hot-field allowlist used to build the command palette; validate the resolved settings class and hot field before calling hot_update_plugin_config.

Useful? React with 👍 / 👎.

Comment thread main_routers/actions_proxy_router.py Fixed
Comment thread main_routers/actions_proxy_router.py Fixed
plugin_id: str | None = None,
) -> list[ActionDescriptor]:
"""返回操作项列表。*plugin_id* 为 ``None`` 时返回所有。"""
...
Comment thread plugin/tests/unit/server/test_action_execution_service.py Fixed
Comment thread plugin/tests/unit/server/test_action_list_actions_provider.py Fixed
Comment thread plugin/tests/integration/test_action_routes.py Fixed
Comment thread plugin/tests/unit/server/test_action_system_provider.py Fixed
Comment thread plugin/tests/integration/test_action_routes.py Fixed
Comment thread plugin/sdk/plugin/base.py Fixed
Comment thread plugin/sdk/plugin/base.py Fixed
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
plugin/sdk/plugin/base.py (1)

151-160: ⚡ Quick win

_last_attachments 属性依赖过于隐式喵~

这个方法在 line 159 回退到 getattr(self, "_last_attachments", None),但是 _last_attachments 从来没有在 __init__ 里初始化过喵。虽然用 getattr 加默认值是安全的,不会抛异常,但这种设计让人摸不着头脑喵!

问题在于:

  • 不知道什么时候、谁会设置 _last_attachments
  • 这个属性不是类接口的明确部分
  • 依赖外部代码隐式设置属性容易出问题喵

建议要么在 __init__ 里初始化 self._last_attachments = [],要么在文档字符串里说明这个属性的生命周期和设置时机喵~ 不...不是本喵看不懂啦,只是为了其他笨蛋开发者着想,哼!

♻️ 建议的修复方案喵

__init__ 方法里初始化这个属性:

     def __init__(self, ctx):
         super().__init__(ctx)
         # Promote plugin-facing helper instead of exposing the shared minimal contract.
         from .runtime import Plugins
 
         self.plugins = Plugins(self.ctx)
         self._memory_client = None
         self._system_info_client = None
         self.i18n = self._load_plugin_i18n()
         self._static_ui_config: dict[str, Any] | None = None
         self._list_actions: list[dict[str, Any]] = []
+        self._last_attachments: list[dict] = []
         self._dynamic_entries: dict[str, dict[str, Any]] = {}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/sdk/plugin/base.py` around lines 151 - 160, get_attachments falls back
to an implicitly-set attribute _last_attachments which is never initialized;
update the class __init__ to set self._last_attachments = [] (or an appropriate
default) so the fallback is explicit and safe, and optionally add a short
docstring/note on when/where _last_attachments is updated; locate the fix in the
class constructor and ensure get_attachments, _last_attachments and __init__ are
consistent.
plugin/sdk/shared/runtime/system_info.py (1)

171-171: 💤 Low value

确认返回空字符串的设计意图喵~

当配置中既没有 user_language_full 也没有 user_language 时,这里返回 Ok("") 而不是 Err,这意味着"未配置语言"被视为成功状态喵。调用方需要检查空字符串来判断是否真的获取到了语言设置喵。

建议确认这个行为是否符合预期,并考虑在文档字符串中说明:返回空字符串表示未配置语言,这样其他开发者就不会误以为出错了喵~ 毕竟傲娇如本喵也会被这种微妙的设计搞迷糊呢,哼!

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/sdk/shared/runtime/system_info.py` at line 171, The function in
plugin/sdk/shared/runtime/system_info.py currently returns Ok("") when neither
user_language_full nor user_language is set; confirm this behavior is
intentional (treating "no language configured" as success) and, if so,
add/extend the docstring for the enclosing function (the one returning Ok(""))
to state clearly that an empty string signals "language not configured" and
callers must check for an empty value; if the intended behavior is instead to
surface an error, change the return to an Err variant and update callers
accordingly.
plugin/server/domain/action_models.py (1)

24-40: ⚡ Quick win

建议添加字段组合验证喵~

当前模型允许任意的可选字段组合,但某些 control 类型需要特定的配套字段喵:

  • control="slider" 应该要求 minmaxstep 存在喵
  • control="dropdown" 应该要求 options 不为空喵
  • control="number" 可能也需要 min/max 范围喵
  • control="toggle" 应该有 current_value

可以考虑添加 Pydantic 的 @model_validator 来强制这些约束,这样能在构造时就发现配置错误,而不是等到运行时才炸掉喵~

💡 可选的验证器示例喵
+from pydantic import model_validator
+
 class ActionDescriptor(BaseModel):
     """描述一个命令面板中的操作项。"""
     
     # ... existing fields ...
+
+    `@model_validator`(mode="after")
+    def validate_control_fields(self) -> "ActionDescriptor":
+        """验证 control 类型与配套字段的一致性喵"""
+        if self.control == "slider":
+            if self.min is None or self.max is None or self.step is None:
+                raise ValueError("slider control requires min, max, and step")
+        elif self.control == "dropdown":
+            if not self.options:
+                raise ValueError("dropdown control requires non-empty options")
+        elif self.control == "number":
+            if self.min is None or self.max is None:
+                raise ValueError("number control requires min and max")
+        return self
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/server/domain/action_models.py` around lines 24 - 40, The model
currently allows invalid field combinations for different control types; add a
Pydantic model-level validator (e.g., a `@model_validator` on the class defined in
plugin/server/domain/action_models.py that declares control, current_value,
options, min, max, step, disabled) to enforce required fields per control:
require min, max and step when control == "slider"; require non-empty options
when control == "dropdown"; require min/max (or at least warn/validate) when
control == "number"; and require current_value when control == "toggle"; the
validator should inspect the control field and raise a ValueError with a clear
message when the related fields are missing or invalid so invalid instances fail
at construction.
plugin/server/application/actions/system_provider.py (1)

223-231: ⚡ Quick win

避免在导航动作里硬编码中文文案与关键词喵

这里把 labelkeywords 固定成中文,会让非中文语言环境下的命令面板搜索与理解变差,建议至少改为中立文案或按当前语言分支生成喵。

♻️ 可参考修改喵
         if _has_static_ui(meta):
             from config import USER_PLUGIN_SERVER_PORT as _ui_port
             actions.append(ActionDescriptor(
                 action_id=f"system:{pid}:open_ui",
                 type="navigation",
-                label=f"打开 {plugin_name} UI",
+                label=f"Open {plugin_name} UI",
                 description="",
                 category=plugin_name,
                 plugin_id=pid,
                 target=f"http://127.0.0.1:{_ui_port}/plugin/{pid}/ui/",
                 open_in="new_tab",
                 icon="↗",
-                keywords=[pid, plugin_name, "ui", "界面"],
+                keywords=[pid, plugin_name, "ui", "open"],
             ))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/server/application/actions/system_provider.py` around lines 223 - 231,
The label and keywords for the navigation action are hardcoded in Chinese;
update the code that builds the action (the dict using label and keywords with
plugin_name and pid) to avoid fixed Chinese text by using a neutral/English
default like "Open {plugin_name} UI" and English keywords (e.g., pid,
plugin_name, "ui", "interface"), or wire it to your app i18n/locale function
(e.g., gettext/t or current locale branch) so the label and keywords are
generated per-language instead of being hardcoded.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugin/sdk/plugin/base.py`:
- Line 114: 当前改动将 data_path() 改为返回 self.config_dir / "data" 导致与旧的
resolve_plugin_data_dir(self.ctx) 不一致并破坏现有插件数据访问;请恢复一致性:在 plugin/base.py 中将
data_path() 的实现改回使用 resolve_plugin_data_dir(self.ctx)(或若你决定统一到
self.config_dir,则同时把使用 resolve_plugin_data_dir 的 plugin_dir 赋值点改为使用
self.config_dir / "data" 并调整所有相关调用与测试),确保引用的唯一标识符为
resolve_plugin_data_dir、data_path、self.config_dir、plugin_dir 和 ctx,并在改动后运行并通过
plugin/tests/unit/sdk/plugin/test_sdk_v2_plugin_base.py 中期望的路径断言。

In `@plugin/server/application/actions/settings_provider.py`:
- Around line 273-279: When building current_value for hot fields in the loop
over settings_cls.model_fields (see _is_hot, field_info.annotation and
current_section usage), don't fall back to the raw sentinel field_info.default
because fields with a default_factory will return PydanticUndefined; instead
call field_info.get_default(call_default_factory=True) when current_section
lacks the key to invoke the factory and obtain a real default. Update the
assignment that sets current_value to use
field_info.get_default(call_default_factory=True) rather than
field_info.default, and add a unit test for a hot field with default_factory to
assert the returned current_value is JSON-serializable and has the expected
default.

---

Nitpick comments:
In `@plugin/sdk/plugin/base.py`:
- Around line 151-160: get_attachments falls back to an implicitly-set attribute
_last_attachments which is never initialized; update the class __init__ to set
self._last_attachments = [] (or an appropriate default) so the fallback is
explicit and safe, and optionally add a short docstring/note on when/where
_last_attachments is updated; locate the fix in the class constructor and ensure
get_attachments, _last_attachments and __init__ are consistent.

In `@plugin/sdk/shared/runtime/system_info.py`:
- Line 171: The function in plugin/sdk/shared/runtime/system_info.py currently
returns Ok("") when neither user_language_full nor user_language is set; confirm
this behavior is intentional (treating "no language configured" as success) and,
if so, add/extend the docstring for the enclosing function (the one returning
Ok("")) to state clearly that an empty string signals "language not configured"
and callers must check for an empty value; if the intended behavior is instead
to surface an error, change the return to an Err variant and update callers
accordingly.

In `@plugin/server/application/actions/system_provider.py`:
- Around line 223-231: The label and keywords for the navigation action are
hardcoded in Chinese; update the code that builds the action (the dict using
label and keywords with plugin_name and pid) to avoid fixed Chinese text by
using a neutral/English default like "Open {plugin_name} UI" and English
keywords (e.g., pid, plugin_name, "ui", "interface"), or wire it to your app
i18n/locale function (e.g., gettext/t or current locale branch) so the label and
keywords are generated per-language instead of being hardcoded.

In `@plugin/server/domain/action_models.py`:
- Around line 24-40: The model currently allows invalid field combinations for
different control types; add a Pydantic model-level validator (e.g., a
`@model_validator` on the class defined in plugin/server/domain/action_models.py
that declares control, current_value, options, min, max, step, disabled) to
enforce required fields per control: require min, max and step when control ==
"slider"; require non-empty options when control == "dropdown"; require min/max
(or at least warn/validate) when control == "number"; and require current_value
when control == "toggle"; the validator should inspect the control field and
raise a ValueError with a clear message when the related fields are missing or
invalid so invalid instances fail at construction.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1527a90c-d41b-42c0-b2af-00686136d5ce

📥 Commits

Reviewing files that changed from the base of the PR and between 4e739ee and 3d6125a.

📒 Files selected for processing (37)
  • app/main_server.py
  • frontend/react-neko-chat/src/App.tsx
  • frontend/react-neko-chat/src/CommandPalette.tsx
  • frontend/react-neko-chat/src/message-schema.test.ts
  • frontend/react-neko-chat/src/message-schema.ts
  • frontend/react-neko-chat/src/styles.css
  • frontend/react-neko-chat/src/test/CommandPalette.test.tsx
  • main_routers/actions_proxy_router.py
  • plugin/sdk/plugin/__init__.py
  • plugin/sdk/plugin/base.py
  • plugin/sdk/plugin/settings.py
  • plugin/sdk/shared/core/decorators.py
  • plugin/sdk/shared/runtime/system_info.py
  • plugin/server/application/actions/__init__.py
  • plugin/server/application/actions/aggregation_service.py
  • plugin/server/application/actions/builtin_provider.py
  • plugin/server/application/actions/execution_service.py
  • plugin/server/application/actions/list_actions_provider.py
  • plugin/server/application/actions/preferences_service.py
  • plugin/server/application/actions/settings_provider.py
  • plugin/server/application/actions/system_provider.py
  • plugin/server/domain/action_models.py
  • plugin/server/domain/action_provider.py
  • plugin/server/http_app.py
  • plugin/server/infrastructure/plugin_settings_resolver.py
  • plugin/server/routes/__init__.py
  • plugin/server/routes/actions.py
  • plugin/server/routes/config.py
  • plugin/tests/integration/test_action_routes.py
  • plugin/tests/unit/server/test_action_aggregation_service.py
  • plugin/tests/unit/server/test_action_execution_service.py
  • plugin/tests/unit/server/test_action_list_actions_provider.py
  • plugin/tests/unit/server/test_action_models.py
  • plugin/tests/unit/server/test_action_settings_provider.py
  • plugin/tests/unit/server/test_action_system_provider.py
  • static/app-react-chat-window.js
  • tests/unit/test_actions_proxy_router.py

Comment thread plugin/sdk/plugin/base.py Outdated
Comment thread plugin/server/application/actions/settings_provider.py
- base.py: restore data_path() to use resolve_plugin_data_dir(self.ctx)
  for backward compatibility with existing plugins
- base.py: initialize _last_attachments in __init__ for explicitness
- settings_provider.py: use field_info.get_default(call_default_factory=True)
  instead of field_info.default to handle default_factory fields correctly
- system_info.py: clarify docstring that Ok('') means language not configured
- system_provider.py: replace hardcoded Chinese label/keywords with English
Comment thread plugin/sdk/plugin/base.py Fixed
Comment thread plugin/sdk/plugin/base.py Fixed
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugin/sdk/plugin/base.py`:
- Around line 153-163: The success path in get_attachments currently returns
result without updating the cache, so self._last_attachments remains stale;
modify get_attachments to assign the retrieved attachments into
self._last_attachments before returning: call result =
self.ctx.get_attachments(), and if result is not None then compute attachments =
list(result) if isinstance(result, list) else [], set self._last_attachments =
attachments, and then return attachments (preserving the try/except and fallback
behavior).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 041eb126-75b1-4013-9e14-5f9ecfc2fef1

📥 Commits

Reviewing files that changed from the base of the PR and between 3d6125a and 0b3de9c.

📒 Files selected for processing (4)
  • plugin/sdk/plugin/base.py
  • plugin/sdk/shared/runtime/system_info.py
  • plugin/server/application/actions/settings_provider.py
  • plugin/server/application/actions/system_provider.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • plugin/sdk/shared/runtime/system_info.py
  • plugin/server/application/actions/system_provider.py
  • plugin/server/application/actions/settings_provider.py

Comment thread plugin/sdk/plugin/base.py
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b3de9c33d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +251 to +253
effective = getattr(host, "get_effective_config", None)
if callable(effective):
cfg = effective()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve temporary setting values when reloading actions

When a hot setting is executed, ActionExecutionService applies it with mode="temporary", but the subsequent action refresh reaches this branch and then falls back to load_plugin_config because no current host implementation exposes get_effective_config (repo-wide rg get_effective_config only finds this call). For any temporary command-palette setting change, the refreshed descriptor therefore reads the persisted TOML value instead of the value just applied in the running plugin, so toggles/sliders snap back to stale values and the next click can send the wrong value again. The provider needs a real source for the child process's effective config or another cache of temporary updates.

Useful? React with 👍 / 👎.

- CommandPalette.tsx: include 'text' in hasInlineWidget set so space key
  is not intercepted by the row's onKeyDown handler
- execution_service.py: validate that the target field is actually a
  hot=True field before dispatching settings update (prevents bypassing
  the hot-field allowlist via crafted action_id)
- settings_provider.py: document the known limitation that temporary
  hot-updates may show stale TOML values until host.get_effective_config
  is implemented; frontend uses ActionExecuteResponse.action as workaround
Comment thread frontend/react-neko-chat/src/CommandPalette.tsx Fixed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2b9fc1f95b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +45 to +47
static_ui_obj = meta.get("static_ui_config")
if not isinstance(static_ui_obj, Mapping):
return False
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reuse static UI metadata inference

For plugins that only provide <plugin dir>/static/index.html and rely on the existing ui_query_service config_path inference, this branch returns false because it requires an explicit static_ui_config. The /plugin/{id}/ui/ route can still serve those UIs via plugin/server/application/plugins/ui_query_service.py, but the command palette will omit the Open UI action; resolve the static UI config through the same helper before checking the directory.

Useful? React with 👍 / 👎.

Comment on lines +199 to +200
current_value=str(current_value) if current_value is not None else None,
options=[str(a) for a in args],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve non-string Literal setting values

When a hot setting is annotated as a non-string Literal such as Literal[1, 2], the descriptor stringifies both the current value and options, and the frontend dropdown posts that string back unchanged. The config hot-update then validates against the original PluginSettings type, so selecting "1" for a Literal[1, 2] field is rejected even though the provider exposed it as editable; keep enough type information to coerce the submitted value back to the literal's original type.

Useful? React with 👍 / 👎.

- base.py: update _last_attachments cache on success path in get_attachments
- base.py: add explanatory comments to empty except blocks
- test_action_execution_service.py: remove unused ActionExecuteResponse import
- test_action_list_actions_provider.py: remove unused threading import
- test_action_routes.py: remove unused Any import, unify module import style
- test_action_system_provider.py: remove unused Mapping import
Comment thread plugin/sdk/plugin/base.py Fixed
Comment thread plugin/server/application/actions/preferences_service.py Fixed
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugin/sdk/plugin/base.py`:
- Around line 157-159: The method currently assigns a list to
self._last_attachments and returns that same list reference (see the if
isinstance(result, list) block and the _last_attachments attribute); change the
return to provide a shallow copy instead of the internal list to avoid exposing
mutable internal state—i.e., after setting self._last_attachments =
list(result), return a copy (e.g., list(self._last_attachments) or
self._last_attachments.copy()) so callers cannot mutate the internal cache.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b431e163-229f-4517-a764-f359766ef8dc

📥 Commits

Reviewing files that changed from the base of the PR and between 2b9fc1f and df806d4.

📒 Files selected for processing (5)
  • plugin/sdk/plugin/base.py
  • plugin/tests/integration/test_action_routes.py
  • plugin/tests/unit/server/test_action_execution_service.py
  • plugin/tests/unit/server/test_action_list_actions_provider.py
  • plugin/tests/unit/server/test_action_system_provider.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • plugin/tests/unit/server/test_action_system_provider.py
  • plugin/tests/unit/server/test_action_execution_service.py
  • plugin/tests/unit/server/test_action_list_actions_provider.py

Comment thread plugin/sdk/plugin/base.py Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: df806d404e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +61 to +62
label = str(action.get("label") or action_id)
description = str(action.get("description") or "")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve localized list action labels

When a plugin defines list_actions.label or description as an i18n mapping, which the existing plugin UI query path accepts and resolves via resolve_i18n_refs, this coerces the mapping to a Python dict string (for example {'en-US': 'Open'}) before sending it to the command palette. Those actions then display and search against the raw repr instead of the localized label; resolve list action metadata through the same i18n helper before building descriptors.

Useful? React with 👍 / 👎.

Comment on lines +245 to +248
monkeypatch.setattr(
"plugin.server.application.actions.execution_service.resolve_settings_class",
lambda pid: None,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update the settings test to use a hot settings class

This test patches resolve_settings_class to return None, but _SettingsActionHandler.execute now validates that a settings class exists and will raise SETTINGS_NOT_FOUND before calling hot_update_plugin_config. With the test dependencies installed, svc.execute(...) fails here instead of reaching the assertions; use a fake PluginSettings class with a hot field, or change this test to assert the new rejection behavior.

Useful? React with 👍 / 👎.

- base.py: return copy of _last_attachments to prevent cache mutation
- base.py: add comment to fetch_user_language except block
- preferences_service.py: add comment to temp file cleanup except block
- test_action_execution_service.py: provide fake PluginSettings with hot
  field to match new validation in _SettingsActionHandler.execute
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0143e3a3cb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

action_id,
)
return None
target = raw_target.strip()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize list-action targets before exposing them

For static list_actions that use the supported {plugin_id} placeholder (the existing plugin UI query path replaces it in plugin/server/application/plugins/ui_query_service.py:481), this provider sends the raw target to the command palette. In that case clicking the command navigates to a literal URL like /plugins/{plugin_id}?tab=panel or https://example.com/{plugin_id} instead of the plugin-specific route, even though the same action works from the plugin list UI; reuse the same normalization before building the descriptor.

Useful? React with 👍 / 👎.

- list_actions_provider: resolve plugin i18n refs for label/description
  and the plugin name itself, mirroring system_provider behavior so
  $i18n mappings no longer surface as raw dict reprs in the palette.
- list_actions_provider: substitute the `{plugin_id}` placeholder in
  navigation / chat_inject targets, mirroring the normalization that
  ui_query_service already applies for the plugin list UI.
- system_provider: replace the local _has_static_ui with a delegate to
  ui_query_service._has_static_ui_from_meta so plugins relying on the
  conventional <plugin dir>/static/index.html inference (no explicit
  static_ui_config) are recognized — matches what /plugin/{id}/ui/
  actually serves.
- test_action_execution_service: drop legacy `class Config: pass` from
  _FakeSettings (pydantic v2 rejects `Config` + `model_config` together,
  introduced in the third-round fix).
- tests: cover plugin_id substitution, $i18n label resolution, and the
  inferred-static-UI path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugin/server/application/actions/list_actions_provider.py`:
- Around line 76-85: The code checks action_id with strip() but uses the
original value when building full_action_id which can keep surrounding
whitespace; normalize and reuse a cleaned action_id instead: coerce
action.get("id") to a string, call .strip() (and optionally .lower() if you want
case-normalization) into a new variable (e.g., cleaned_action_id or reuse
action_id after assignment), validate that cleaned value is non-empty, and build
full_action_id = f"{plugin_id}:{cleaned_action_id}" (update any subsequent uses
to reference the cleaned/normalized identifier).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 616f48f5-3a2d-4525-a2f0-54688d512290

📥 Commits

Reviewing files that changed from the base of the PR and between 0143e3a and 4a0bcb9.

📒 Files selected for processing (5)
  • plugin/server/application/actions/list_actions_provider.py
  • plugin/server/application/actions/system_provider.py
  • plugin/tests/unit/server/test_action_execution_service.py
  • plugin/tests/unit/server/test_action_list_actions_provider.py
  • plugin/tests/unit/server/test_action_system_provider.py
✅ Files skipped from review due to trivial changes (1)
  • plugin/tests/unit/server/test_action_list_actions_provider.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • plugin/server/application/actions/system_provider.py
  • plugin/tests/unit/server/test_action_system_provider.py
  • plugin/tests/unit/server/test_action_execution_service.py

Comment thread plugin/server/application/actions/list_actions_provider.py Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4a0bcb9641

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +308 to +312
except Exception as exc:
raise ServerDomainError(
code="ENTRY_TRIGGER_FAILED",
message=f"Failed to trigger entry '{entry_id}': {exc}",
status_code=500,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Re-raise domain errors in entry execution handlers

The broad except Exception here wraps intentionally raised ServerDomainErrors (for example ENTRY_TRIGGER_UNSUPPORTED/ENTRY_TOGGLE_UNSUPPORTED) into a generic 500 ENTRY_*_FAILED, which changes expected status codes and hides actionable error codes from clients. This makes unsupported host capabilities look like internal failures; ServerDomainError should be allowed to propagate unchanged before the generic fallback wrapping.

Useful? React with 👍 / 👎.

Comment on lines +162 to +164
handler = self._DISPATCH.get(action)
if handler is not None:
return await handler(self, plugin_id, action_id, value)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate system action IDs have exact lifecycle shape

Lifecycle dispatch accepts any system:{plugin_id}:{action}:... string because it only checks parts[2], so malformed IDs like system:demo:stop:unexpected still execute privileged lifecycle operations. The route should require exactly three segments for lifecycle actions and reserve extra segments only for the entry form to avoid executing unintended actions from malformed/crafted IDs.

Useful? React with 👍 / 👎.

continue
meta: dict[str, Any] = dict(meta_raw)

plugin_name = str(meta.get("name") or pid)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve localized plugin names in settings actions

Settings actions derive plugin_name with str(meta.get("name")), so plugins that provide i18n name mappings render categories/keywords as raw dict strings instead of localized text. Other action providers in this change already resolve i18n metadata, so this creates an inconsistent UX where settings entries for localized plugins are mislabeled and harder to search.

Useful? React with 👍 / 👎.

- execution_service: re-raise ServerDomainError before the broad except in
  entry button/toggle paths so intentional ENTRY_TRIGGER_UNSUPPORTED /
  ENTRY_TOGGLE_UNSUPPORTED (501) are not collapsed into a generic
  ENTRY_*_FAILED (500), which had hidden the actionable code and status.
- execution_service: lifecycle dispatch (start/stop/reload/toggle/profile)
  now requires exactly 3 segments — crafted ids like
  `system:demo:stop:unexpected` no longer reach the privileged op.
- settings_provider: resolve plugin name through plugin i18n before
  building descriptors, matching list_actions_provider and system_provider
  so $i18n refs no longer leak as dict reprs into the command palette
  category / keywords.
- list_actions_provider: normalize action_id with strip() before joining
  with plugin_id so a raw " foo " no longer produces "demo: foo " ids.
- tests: cover the unsupported-error preservation, lifecycle segment
  rejection, settings-provider i18n resolution, and id whitespace
  normalization.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7144b1f2cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread main_routers/actions_proxy_router.py Outdated
try:
base = await _resolve_user_plugin_base()
async with httpx.AsyncClient(timeout=10.0, proxy=None, trust_env=False) as client:
resp = await client.post(f"{base}/chat/actions/{action_id}/execute", json=body)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge URL-encode forwarded action IDs in proxy execute path

proxy_chat_action_execute forwards action_id by direct string interpolation, so reserved URL characters in IDs (e.g. ?, #, %) change the outgoing request path/query and the plugin server can receive a different action ID or a 404. This is reachable because action IDs are plugin-defined and not restricted to URL-safe characters (the list-action provider only enforces non-empty strings), so a valid configured action can become unexecutable through the main-server proxy.

Useful? React with 👍 / 👎.

if not isinstance(meta_raw, Mapping):
continue
meta: dict[str, Any] = dict(meta_raw)
plugin_name = str(meta.get("name") or pid)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve localized plugin names for builtin action labels

Builtin lifecycle actions derive plugin_name with str(meta.get("name") or pid), which turns i18n name mappings into raw dict strings (for example {'$i18n': ...}) in labels and keywords. In that case command names become unreadable and search matching degrades for localized plugins, unlike other action providers in this change that resolve i18n metadata before rendering descriptors.

Useful? React with 👍 / 👎.

- actions_proxy_router: percent-encode action_id before interpolating it
  into the outgoing /chat/actions/{action_id}/execute path. Reserved URL
  chars (`?`, `#`, `%`, `/`) in plugin-defined IDs would otherwise
  reinterpret the request as path+query/fragment and reach the plugin
  server as a different (or 404) action. `:` stays unencoded so the
  canonical `system:demo:toggle` form remains readable.
- builtin_provider: resolve plugin name through plugin i18n (matching
  list_actions, system, and settings providers from the earlier rounds)
  so `$i18n` refs no longer leak as raw dict reprs into lifecycle action
  labels and keywords.
- tests: cover URL encoding for `?` / `#` / `%` while preserving `:`,
  and add a dedicated builtin_provider test module covering lifecycle
  emission plus i18n ref resolution.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
try:
response = await client.get(f"{base}/available")
if response.is_success:
_USER_PLUGIN_BASE_CACHE = (base, now)
continue

fallback = str(USER_PLUGIN_BASE or _USER_PLUGIN_DEFAULT_BASE).rstrip("/")
_USER_PLUGIN_BASE_CACHE = (fallback, now)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugin/tests/unit/server/test_action_builtin_provider.py`:
- Around line 89-90: The test currently uses all(a.plugin_id == "b" for a in
actions) which will pass on an empty list; update the assertion to prevent false
positives by first asserting actions is non-empty (e.g., assert actions) and
then keeping the all(...) check, or replace both with a precise check of the
returned action IDs (e.g., compare {a.action_id for a in actions} to the
expected set). Apply this change around the call to _collect and references to
actions, plugin_id and action_id.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2ab289e6-7271-4a2a-9dcd-1c5d4082c67c

📥 Commits

Reviewing files that changed from the base of the PR and between 7144b1f and 2c20738.

📒 Files selected for processing (4)
  • main_routers/actions_proxy_router.py
  • plugin/server/application/actions/builtin_provider.py
  • plugin/tests/unit/server/test_action_builtin_provider.py
  • tests/unit/test_actions_proxy_router.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/unit/test_actions_proxy_router.py
  • plugin/server/application/actions/builtin_provider.py
  • main_routers/actions_proxy_router.py

Comment thread plugin/tests/unit/server/test_action_builtin_provider.py
`all(a.plugin_id == "b" for a in actions)` returns True on `actions == []`,
so a regression that drops the filter output entirely would still pass.
Add a non-empty guard before the all() check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eb505a375c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +432 to +433
if action_id.startswith("system:"):
return await self._system_handler.execute(action_id, value)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reserve system plugin IDs before dispatching by prefix

ActionExecutionService.execute routes every action whose ID starts with system: to the lifecycle handler, so any plugin with plugin_id == "system" will have all of its settings/entry action IDs misclassified and become unexecutable (or target the wrong lifecycle path). Because dispatch is purely prefix-based here, this introduces a hard collision with a valid plugin namespace and breaks command palette actions for that plugin.

Useful? React with 👍 / 👎.

item={item}
loading={!!loadingMap[item.action_id]}
error={errorMap[item.action_id] ?? null}
highlighted={false}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve keyboard activation in grouped plugin view

Keyboard navigation is wired to activate the highlighted row (.cp-row-highlighted.cp-row-clickable) on Enter, but grouped rendering forces every row to highlighted={false}. In "按插件" view, ArrowUp/ArrowDown updates highlightIdx but no row ever receives the highlight class, so Enter cannot execute any command via keyboard.

Useful? React with 👍 / 👎.

- execution_service: split dispatch by structure rather than the bare
  `system:` prefix, so a plugin whose `plugin_id == "system"` is no
  longer a tombstone. The new order:
    1. `{plugin_id}:settings:{field}` (wins, even for plugin "system")
    2. `system:{plugin_id}:{lifecycle|entry...}` ONLY when parts[2] is a
       known lifecycle keyword (start/stop/reload/toggle/profile/entry)
    3. `{plugin_id}:{action_id}` fall-through to list-action handler.
  Existing tests that asserted ACTION_NOT_FOUND from the system handler
  for `system:bad` / `system:demo:explode` now correctly land in the
  list-action handler (ACTION_NOT_IMPLEMENTED) — the system handler is
  no longer reserved for ambiguous strings.
- CommandPalette: pipe `highlightedActionId` from CommandPalette into
  PluginCard so grouped view rows actually receive the highlight class.
  PluginCard auto-expands while one of its rows is highlighted so
  keyboard Enter resolves a visible `.cp-row-highlighted.cp-row-clickable`
  (the parent's Enter handler activates via querySelector + .click()).
- tests: cover plugin "system" list/settings routing (including the
  `system:settings:start` collision case where the field name is also a
  lifecycle keyword), update the two stale system-handler tests, and add
  a Vitest case asserting Enter activation works in `byPlugin` view.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread frontend/react-neko-chat/src/CommandPalette.tsx Fixed
CodeQL's `Superfluous trailing arguments` rule infers the constructor
signature from this stub and has been re-firing on every push to flag
`new ResizeObserver(measure)` in CommandPalette.tsx — even though that
matches the real DOM API which takes a `ResizeObserverCallback`.

Give the stub an optional `cb?: ResizeObserverCallback` parameter so its
shape matches the real type, silencing the false positive without
touching production code. The stub still no-ops; the cb is never
invoked, which is fine for the jsdom test environment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 00f9d53807

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +279 to +284
if (prop?.type === 'number' || prop?.type === 'integer') {
args[key] = Number(raw) || 0;
} else if (prop?.type === 'boolean') {
args[key] = raw === 'true' || raw === '1';
} else {
args[key] = raw;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Omit untouched parameter fields instead of coercing defaults

ParamForm.submit always emits every schema property and coerces blank inputs to concrete values (Number(raw) || 0, false, or ""). For entries with optional parameters, submitting without filling a field no longer preserves server/plugin defaults; it actively overrides them with 0/false/empty string, which can change behavior or trigger validation errors in handlers expecting missing fields to be omitted.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
plugin/tests/unit/server/test_action_execution_service.py (1)

411-415: ⚡ Quick win

这两个回归用例还缺少对 resp.action 的断言喵

现在它们只验证了 hot-update 的入参有没有走对,但前面那个 namespace bug 真正坏掉的是返回载荷里的 ActionExecuteResponse.action。也就是说,哪怕 resp.action is None,这两条测试现在还是会绿掉,兜不住这次回归喵。

建议这里给 _build_service() 注入一个带目标 ActionDescriptor_FakeAggregationService,再补一条 assert resp.action is not None / assert resp.action.action_id == "system:settings:...",这样才能把这个场景真正锁住喵。

Also applies to: 446-450

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/tests/unit/server/test_action_execution_service.py` around lines 411 -
415, 测试目前只验证了 hot-update 的入参,但没有断言返回载荷里的 ActionExecuteResponse.action,这会漏掉
namespace 相关的回归;在调用 _build_service() 时注入一个返回带目标 ActionDescriptor 的
_FakeAggregationService(保证执行时会填充 resp.action),并在原有断言后补上 assert resp.action is
not None 以及 assert resp.action.action_id == "system:settings:enabled"(或与该用例对应的
action_id),以确保 resp.action 被正确返回并锁住该回归场景。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugin/server/application/actions/execution_service.py`:
- Around line 50-53: The current _find_action() misparses action_id like
"system:settings:enabled" as a system lifecycle action and sets plugin_id to
"settings", causing aggregate_actions(plugin_id=...) to miss the updated
descriptor and ActionExecuteResponse.action to be None; fix by changing the
parsing logic in _find_action(): when action_id.startswith("system:") split into
parts but only treat it as a system lifecycle action if parts[1] is not
"settings" (i.e., check parts[1] != "settings" before assigning plugin_id =
parts[1]), or better yet modify _find_action() signature to accept an explicit
plugin_id parameter (and prefer that over guessing) so
aggregate_actions(plugin_id=...) uses the correct plugin id and the action
lookup returns the updated descriptor for ActionExecuteResponse.action.

---

Nitpick comments:
In `@plugin/tests/unit/server/test_action_execution_service.py`:
- Around line 411-415: 测试目前只验证了 hot-update 的入参,但没有断言返回载荷里的
ActionExecuteResponse.action,这会漏掉 namespace 相关的回归;在调用 _build_service()
时注入一个返回带目标 ActionDescriptor 的 _FakeAggregationService(保证执行时会填充
resp.action),并在原有断言后补上 assert resp.action is not None 以及 assert
resp.action.action_id == "system:settings:enabled"(或与该用例对应的 action_id),以确保
resp.action 被正确返回并锁住该回归场景。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7e7425c6-019b-4223-ba3d-c65815b59bed

📥 Commits

Reviewing files that changed from the base of the PR and between eb505a3 and 00f9d53.

📒 Files selected for processing (5)
  • frontend/react-neko-chat/src/CommandPalette.tsx
  • frontend/react-neko-chat/src/test/CommandPalette.test.tsx
  • frontend/react-neko-chat/src/test/setup.ts
  • plugin/server/application/actions/execution_service.py
  • plugin/tests/unit/server/test_action_execution_service.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/react-neko-chat/src/test/CommandPalette.test.tsx
  • frontend/react-neko-chat/src/CommandPalette.tsx

Comment thread plugin/server/application/actions/execution_service.py Outdated
Hongzhi Wen and others added 2 commits May 13, 2026 04:37
Two findings landed nearly simultaneously and share the same theme as
the previous round (preserve caller intent / don't override defaults).

- ParamForm.submit (CommandPalette.tsx): stop coercing untouched optional
  parameter fields to ``0`` / ``false`` / ``""``. Skip blank inputs so
  the server/plugin can apply its own default; numeric strings that fail
  to parse are also skipped. Filled fields are still emitted verbatim.
- execution_service._find_action: ``system:`` prefix dispatch had the
  same bug previously fixed in ``execute()`` — it always read
  ``parts[1]`` as plugin_id, which silently misclassified plugin "system"
  settings (e.g. ``system:settings:enabled`` looked like a "settings"
  plugin lifecycle action) and caused the re-fetched descriptor to come
  back ``None``. Extracted the structural mapping into a shared
  ``_plugin_id_from_action_id`` helper that mirrors ``execute()`` and
  promoted ``_SYSTEM_LIFECYCLE_KEYS`` to module level so dispatch and
  reverse lookup stay aligned.
- tests: ParamForm omit-blank and verbatim-emit cases; reverse-lookup
  cases for lifecycle / plugin "system" settings / normal plugin
  settings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…se lookup

Two threads from this round of review:

- coderabbit nitpick: the previous regression tests for plugin "system"
  only asserted on hot_update arguments, not on the returned
  ActionExecuteResponse.action. If _find_action had still been buggy,
  resp.action would silently be None and the tests would still pass.
  Inject a _FakeAggregationService carrying the matching descriptor and
  assert resp.action.action_id, locking the response payload.
- Fix found while writing the assertion: _plugin_id_from_action_id had
  the same settings-vs-lifecycle precedence bug that execute() had —
  ``system:settings:start`` (field name collides with a lifecycle
  keyword) fell into the lifecycle shortcut and returned "settings" as
  plugin_id. Check the {pid}:settings:{field} structural form before the
  lifecycle shortcut, mirroring execute(). Added a dedicated precedence
  case to TestPluginIdFromActionId.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/react-neko-chat/src/test/CommandPalette.test.tsx`:
- Around line 222-229: The test currently only checks visibility for the hidden
item 'demo:greet' but must also assert its "greyed" state; after rendering via
renderPalette(allItems, prefs) and typing into the input (placeholder
'搜索操作...'), locate the 'Greet' item (screen.getByText('Greet')) and assert it
has the dimmed CSS class used by the palette (e.g., palette-item--dimmed) or the
accessible state (e.g., aria-disabled="true") so the test verifies the
visual/semantic "greyed" behavior for hidden items as intended.
- Line 262: Replace the DOM-traversal click that uses label.closest('.cp-row')
in the test with an accessible query: use screen.getByRole('button', { name:
<visible label text> }) and pass that node to fireEvent.click; specifically,
locate where fireEvent.click(label.closest('.cp-row')!) is used in
CommandPalette.test and change it to fireEvent.click(screen.getByRole('button',
{ name: '...' })) (use the actual button label text from the test) so the test
relies on role/name queries instead of the .cp-row class.

In `@plugin/server/application/actions/execution_service.py`:
- Around line 419-433: The current _ListActionHandler.execute unconditionally
raises ACTION_NOT_IMPLEMENTED, preventing any {plugin_id}:{action_id} list
actions from running; replace the hard-coded ServerDomainError with a call that
dispatches execution to the actual list action provider (e.g., use the
ListActionsProvider instance or the provider method that executes actions such
as execute_action/handle_action) passing action_id and value, await its response
and return an ActionExecuteResponse; propagate provider errors as appropriate
(map to ACTION_EXECUTION_FAILED with details) instead of 501, and apply the same
fix to the equivalent handler code referenced around lines 488-491 so list
actions are executed rather than always returning not-implemented.
- Around line 293-305: After calling
set_plugin_active_profile()/set_plugin_active_profile (the profile change), if
await self._lifecycle.reload_plugin(plugin_id) raises, do not return an
unqualified success; catch the exception, log the full error (keep
logger.warning but include exc), and return an ActionExecuteResponse that
clearly indicates a partial failure (e.g., success=False or a dedicated partial
status) with a message like "Profile updated to '<profile_name>' but reload
failed for plugin <plugin_id>; plugin will not use the new profile until manual
restart/reload" so callers/front-end aren’t misled—update the except block
around reload_plugin to construct and return that ActionExecuteResponse instead
of falling through to the full-success return.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 59d15e36-e95d-4335-b566-bea14d220ee0

📥 Commits

Reviewing files that changed from the base of the PR and between 00f9d53 and bf27618.

📒 Files selected for processing (4)
  • frontend/react-neko-chat/src/CommandPalette.tsx
  • frontend/react-neko-chat/src/test/CommandPalette.test.tsx
  • plugin/server/application/actions/execution_service.py
  • plugin/tests/unit/server/test_action_execution_service.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • plugin/tests/unit/server/test_action_execution_service.py
  • frontend/react-neko-chat/src/CommandPalette.tsx

Comment thread frontend/react-neko-chat/src/test/CommandPalette.test.tsx
Comment thread frontend/react-neko-chat/src/test/CommandPalette.test.tsx
Comment thread plugin/server/application/actions/execution_service.py Outdated
Comment thread plugin/server/application/actions/execution_service.py
- execution_service._profile: when set_plugin_active_profile succeeds
  but reload_plugin fails, the config DID change but the running plugin
  is still on the old profile. The previous code returned a misleading
  "Profile switched" success message; now the message surfaces the
  reload failure and tells the user a manual reload is needed. success
  stays True because the persisted profile did change.
- CommandPalette.test.tsx: the 'hidden items (greyed) when searching'
  case only asserted visibility, so a style regression that drops the
  greyed treatment would have left it green. Lock the is-hidden class
  on the row wrap to actually cover the visual contract.
- tests: add a reload-failure regression test that asserts the message
  surfaces both the profile name and the reload error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread plugin/tests/unit/server/test_action_execution_service.py Fixed
CodeQL flagged the dead inner function — I'd left an unused async stub
above the actual lambda patch. Production calls
``set_plugin_active_profile`` via ``asyncio.to_thread``, so the patch
needs a sync callable anyway; the lambda is correct, the async def was
just leftover scaffolding. Comment now explains the sync requirement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@wehos
Copy link
Copy Markdown
Contributor

wehos commented May 12, 2026

我只想知道 肘击完还能用吗现在

LocalBuilder added 2 commits May 13, 2026 23:51
- Match the ⌘ glyph color to the toolbar's #44b7fe blue
- Add hover opacity transition and active state background
- Dark mode active state matches emoji button pattern
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d9b172712e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


// "按插件" mode: show all, no filter tabs
if (viewMode === 'byPlugin') {
const sorted = [...visible].sort(sortByPriority);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Align grouped keyboard order with rendered rows

In byPlugin view with items from multiple plugins whose global priority/label sort interleaves them (for example A1, B1, A2), displayItems remains in that global order while renderGrouped clusters rows by plugin. Since ArrowUp/ArrowDown indexes displayItems but the DOM order is grouped, the highlight can jump to a non-adjacent visual row and Enter can activate a row that is not the next one users see; build displayItems in the same grouped order used for rendering or drive navigation from the rendered row order.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a231cc3. In byPlugin mode, non-search displayItems are now flattened from the same plugin-grouped order used by rendering, while search keeps the existing flat sorted order. Added a regression test for interleaved cross-plugin priorities and verified with npm test -- CommandPalette.test.tsx plus npm run typecheck.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a231cc3be7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

description="",
category=plugin_name,
plugin_id=pid,
target=f"http://127.0.0.1:{_ui_port}/plugin/{pid}/ui/",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Build static UI target from server host, not loopback

The Open UI action hardcodes http://127.0.0.1:<port>/plugin/{id}/ui/, which only works when the browser is running on the same machine as the plugin server. In remote/LAN access, the user’s browser resolves 127.0.0.1 to its own device, so clicking the command opens a dead local URL instead of the server plugin UI. This breaks command-palette UI navigation for non-local clients; construct the URL from the current host (or a proxied route) rather than fixed loopback.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0b36633. The quick-action navigation path now rewrites loopback plugin UI URLs to the current page host when the chat window is being accessed from a remote/LAN host, while preserving the plugin server port and leaving local/non-plugin targets unchanged. Added App-level regression coverage for the host rewrite and unchanged local cases.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b36633012

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +281 to +285
except Exception as exc:
raise ServerDomainError(
code="PLUGIN_PROFILE_ACTIVATE_FAILED",
message=f"Failed to set active profile '{profile_name}'",
status_code=500,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve profile activation status codes

set_plugin_active_profile can raise client-facing errors (for example, missing profiles are raised as 404), but this broad except Exception rewrites all of them to PLUGIN_PROFILE_ACTIVATE_FAILED with status 500. In that scenario, callers receive an internal-error response for invalid input, which breaks actionable UX and error handling; preserve known exception status/code paths before the generic fallback.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d5495ea. Profile activation now lets existing ServerDomainError paths propagate before the generic fallback, so client-facing errors such as missing profiles keep their original code/status instead of being rewritten to PLUGIN_PROFILE_ACTIVATE_FAILED/500. Added a regression test for the 404 preservation path.

icon=str(qa_icon) if qa_icon else "⚡",
keywords=[pid, plugin_name, str(entry_name)],
quick_action=bool(is_quick),
priority=int(qa_priority) if is_quick else 0,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid crashing provider on invalid quick-action priority

priority=int(qa_priority) can raise ValueError when plugin metadata carries a non-numeric priority (e.g., mis-typed decorator input or mutated metadata). That exception aborts SystemActionProvider, and because aggregation catches failures per provider, one bad entry removes all system actions from the response instead of degrading just that item; use safe coercion with a fallback per action.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d5495ea. Quick-action priority coercion now falls back to 0 on TypeError/ValueError, so malformed plugin metadata cannot abort the whole SystemActionProvider collection. Added a regression test covering a non-numeric quick_action priority.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4afbf0c565

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

continue
descriptor = _map_list_action(pid, plugin_name, resolved_action)
if descriptor is not None:
actions.append(descriptor)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Deduplicate list actions before appending

When a plugin's metadata contains duplicate list_actions IDs (possible via static metadata or set_list_actions()), this provider appends both descriptors with the same action_id, while the existing plugin-list UI path deduplicates IDs before returning actions. In the command palette those duplicate IDs are used as React keys and preference/recent identifiers, so hiding or pinning one action also affects the other and rendering can become unstable; keep a per-plugin seen set before appending descriptors.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cbb1826f3f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread plugin/sdk/plugin/base.py
return await self.ctx.export_push(**kwargs)

async def export_push_image(self, **kwargs: Any) -> object:
return await self.ctx.export_push_image(**kwargs)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Implement export_push_image on the context

When plugin authors call the newly exposed await self.export_push_image(...), this forwards to self.ctx.export_push_image, but the context classes exposed in this repo only provide the generic export_push/binary helpers (rg export_push_image only finds this wrapper). In any plugin using this API, it raises AttributeError instead of exporting the image; either add the context method or route this wrapper through the existing export/push-message API.

Useful? React with 👍 / 👎.

Comment thread plugin/sdk/plugin/base.py
def get_attachments(self) -> list[dict]:
"""Return image attachments forwarded from the user's chat message."""
try:
result = self.ctx.get_attachments()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wire get_attachments to a real context source

For plugins that try to read image attachments via the new get_attachments() helper, this calls self.ctx.get_attachments(), but rg get_attachments shows no context/host implementation and _last_attachments is only populated after that call succeeds. As a result the helper always falls back to the initial empty cache, so plugins cannot access forwarded chat attachments even when the user sent images.

Useful? React with 👍 / 👎.

@wehos wehos marked this pull request as draft May 23, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants